#include "smShader.h"
#include "smSourceCode.h"
#include "smUniform.h"
#include "smUniform1i.h"
#include "smUniform1iv.h"
#include "smUniform1f.h"
#include "smUniform1fv.h"
#include "smUniform3f.h"
#include "smUniformMatrix3f.h"
#include "smUniformMatrix4f.h"
#include "smTexture.h"
#include "smTexture1D.h"
#include "smTexture2D.h"
#include "smTexture2DLod.h"
#include "smTexture3D.h"
#include "smTexture3DLod.h"

#include <fstream>
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

namespace ShaderManager
{

smShader::smShader(const char* vertex_shader_name, const char* fragment_shader_name)
:
vertex_shader_path(vertex_shader_name),
tessellation_control_shader_path(""),
tessellation_evaluation_shader_path(""),
geometry_shader_path(""),
fragment_shader_path(fragment_shader_name),
program_id(0)
{
	LoadProgram();
}

smShader::smShader(const char* vertex_shader_name, 
					const char* tessellation_control_shader_name, 
					const char* tessellation_evaluation_shader_name,
					const char* geometry_shader_name,
					const char* fragment_shader_name)
:
vertex_shader_path(vertex_shader_name),
tessellation_control_shader_path(tessellation_control_shader_name),
tessellation_evaluation_shader_path(tessellation_evaluation_shader_name),
geometry_shader_path(geometry_shader_name),
fragment_shader_path(fragment_shader_name),
program_id(0)
{
	LoadProgram();
}

smShader::~smShader()
{
	if(program_id != 0)
		glDeleteProgram(program_id);
}

void smShader::Activate()
{
	//activer program
	glUseProgram(program_id);

	// activer textures
	for(unsigned int i=0 ; i<texture.size() ; ++i)
	{
		texture[i]->active();
		texture[i]->bind();
	}
	
	// update uniform
	for(unsigned int i=0 ; i<uniform.size() ; ++i)
	{
		uniform[i]->load();
	}

}
void smShader::Deactivate()
{
	//desactiver textures
	for(unsigned int i=0 ; i<texture.size() ; ++i)
	{
		texture[i]->active();
		texture[i]->unbind();
	}

	// desactiver program
	glUseProgram(0);
}

void smShader::addUniform1i(const char* uniform_name, const int *val)
{
	smUniform1i *smuniform1i = new smUniform1i(program_id, uniform_name, val);
	uniform.push_back(static_cast<smUniform*> (smuniform1i));
}

void smShader::addUniform1iv(const char* uniform_name, const int *val, const int size)
{
	smUniform1iv *smuniform1iv = new smUniform1iv(program_id, uniform_name, val, size);
	uniform.push_back(static_cast<smUniform*> (smuniform1iv));
}

void smShader::addUniform1f(const char* uniform_name, const float *val)
{
	smUniform1f *smuniform1f = new smUniform1f(program_id, uniform_name, val);
	uniform.push_back(static_cast<smUniform*> (smuniform1f));
}

void smShader::addUniform1fv(const char* uniform_name, const float *val, const int size)
{
	smUniform1fv *smuniform1fv = new smUniform1fv(program_id, uniform_name, val, size);
	uniform.push_back(static_cast<smUniform*> (smuniform1fv));
}

void smShader::addUniform3f(const char* uniform_name, const float tab[3])
{
	smUniform3f *smuniform3f = new smUniform3f(program_id, uniform_name, tab);
	uniform.push_back(static_cast<smUniform*> (smuniform3f));
}

void smShader::addUniformMatrix3f(const char* uniform_name, const float tab[9])
{
	smUniformMatrix3f *shaderManagerMatrix3f = new smUniformMatrix3f(program_id, uniform_name, tab);
	uniform.push_back(static_cast<smUniform*> (shaderManagerMatrix3f));
}

void smShader::addUniformMatrix4f(const char* uniform_name, const float tab[16])
{
	smUniformMatrix4f *shaderManagerMatrix4f = new smUniformMatrix4f(program_id, uniform_name, tab);
	uniform.push_back(static_cast<smUniform*> (shaderManagerMatrix4f));
}

void smShader::addTexture(smTexture* smtexture)
{
	texture.push_back(smtexture);
}

GLuint smShader::LoadShader(GLenum type, const string& filename)
{
	smSourceCode sc(filename);
    GLuint shader = 0;
    GLsizei logsize = 0;
    GLint compile_status = GL_TRUE;
    char *log = NULL;
    char *src = NULL;
    
    /* creation d'un shader de sommet */
    shader = glCreateShader(type);
    if(shader == 0)
    {
        fprintf(stderr, "impossible de creer le shader\n");
        return 0;
    }
    
    /* chargement du code source */
	src = sc.getCode();
    if(src == NULL)
    {
        /* theoriquement, la fonction LoadSource a deja affiche un message
           d'erreur, nous nous contenterons de supprimer notre shader
           et de retourner 0 */
        
        glDeleteShader(shader);
        return 0;
    }
    
    /* assignation du code source */
    glShaderSource(shader, 1, (const GLchar**)&src, NULL);
        
    /* compilation du shader */
    glCompileShader(shader);
    
    /* liberation de la memoire du code source */
    delete [] src;
    src = NULL;
    
    /* verification du succes de la compilation */
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
    if(compile_status != GL_TRUE)
    {
        /* erreur a la compilation recuperation du log d'erreur */
        
        /* on recupere la taille du message d'erreur */
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logsize);
        
        /* on alloue un esapce memoire dans lequel OpenGL ecrira le message */
        log = (char*)malloc(logsize + 1);
        if(log == NULL)
        {
            fprintf(stderr, "impossible d'allouer de la memoire!\n");
            return 0;
        }
        /* initialisation du contenu */
        memset(log, '\0', logsize + 1);
        
        glGetShaderInfoLog(shader, logsize, &logsize, log);
		sc.displayOpenglLog(log);
        
        /* ne pas oublier de liberer la memoire et notre shader */
        free(log);
        glDeleteShader(shader);
        
        return 0;
    }
    
    return shader;
}

void smShader::LoadProgram()
{
	if(program_id != 0)
	{
		glDeleteProgram(program_id);
	}

    GLuint vs = 0, tsc = 0, tse = 0, gs = 0, ps = 0;
    GLint link_status = GL_TRUE;
    GLint logsize = 0;
    char *log = NULL; 
    
    /* chargement des shaders */

	vs = LoadShader(GL_VERTEX_SHADER, vertex_shader_path);
	if(vs == 0) return;

	ps = LoadShader(GL_FRAGMENT_SHADER, fragment_shader_path);
    if(ps == 0)
	{
		glDeleteShader(vs);
        return;
	}

	if( !geometry_shader_path.empty() )
	{
		gs = LoadShader(GL_GEOMETRY_SHADER, geometry_shader_path);
		if(gs == 0) {glDeleteShader(vs); glDeleteShader(ps); return;}
	}

	if( !tessellation_control_shader_path.empty() && !tessellation_evaluation_shader_path.empty())
	{
		tsc = LoadShader(GL_TESS_CONTROL_SHADER, tessellation_control_shader_path);
		if(tsc == 0) {glDeleteShader(vs); glDeleteShader(ps); return;}
		tse = LoadShader(GL_TESS_EVALUATION_SHADER, tessellation_evaluation_shader_path);
		if(tse == 0) {glDeleteShader(vs); glDeleteShader(ps); glDeleteShader(tsc); return;}	
	}

    /* creation du program */
    program_id = glCreateProgram();
    
    /* on envoie nos shaders a notre program */
    if(vs)
        glAttachShader(program_id, vs);
    if(ps)
        glAttachShader(program_id, ps);
	if(tsc)
		glAttachShader(program_id, tsc);
	if(tse)
		glAttachShader(program_id, tse);
	if(gs)
		glAttachShader(program_id, gs);

    /* on lie le tout */
    glLinkProgram(program_id);
    
    /* on verifie que tout s'est bien passe */
    glGetProgramiv(program_id, GL_LINK_STATUS, &link_status);
    if(link_status != GL_TRUE)
    {
        glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &logsize);
        log = (char*)malloc(logsize + 1);
        if(log == NULL)
        {
            glDeleteProgram(program_id);
            glDeleteShader(vs);
            glDeleteShader(ps);
            glDeleteShader(vs);
			glDeleteShader(gs);
            glDeleteShader(ps);

            fprintf(stderr, "impossible d'allouer de la memoire!\n");
            return;
        }
        memset(log, '\0', logsize + 1);
        glGetProgramInfoLog(program_id, logsize, &logsize, log);
        
        fprintf(stderr, "impossible de lier le program :\n%s", log);
        
        free(log);
        glDeleteProgram(program_id);
        glDeleteShader(vs);
        glDeleteShader(ps);
        glDeleteShader(tsc);
        glDeleteShader(tse);
		glDeleteShader(gs);
        return;
    }
    
    /* les shaders sont dans le program maintenant, on en a plus besoin */
    glDeleteShader(vs);
    glDeleteShader(ps);
    glDeleteShader(tsc);
    glDeleteShader(tse);
	glDeleteShader(gs);

	for(unsigned int i=0 ; i<uniform.size() ; i++)
	{
		uniform[i]->update(program_id);
	}
}




}